home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / ResourceBundle.java < prev    next >
Text File  |  1998-09-22  |  21KB  |  539 lines

  1. /*
  2.  * @(#)ResourceBundle.java    1.22 98/01/20
  3.  *
  4.  * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
  5.  * (C) Copyright IBM Corp. 1996 - All Rights Reserved
  6.  *
  7.  * Portions copyright (c) 1996 Sun Microsystems, Inc. All Rights Reserved.
  8.  *
  9.  *   The original version of this source code and documentation is copyrighted
  10.  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  11.  * materials are provided under terms of a License Agreement between Taligent
  12.  * and Sun. This technology is protected by multiple US and International
  13.  * patents. This notice and attribution to Taligent may not be removed.
  14.  *   Taligent is a registered trademark of Taligent, Inc.
  15.  *
  16.  * Permission to use, copy, modify, and distribute this software
  17.  * and its documentation for NON-COMMERCIAL purposes and without
  18.  * fee is hereby granted provided that this copyright notice
  19.  * appears in all copies. Please refer to the file "copyright.html"
  20.  * for further important copyright and licensing information.
  21.  *
  22.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  23.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  24.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  25.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  26.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  27.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  28.  *
  29.  */
  30. package java.util;
  31.  
  32. import java.io.InputStream;
  33. import java.io.FileInputStream;
  34. import java.util.Hashtable;
  35.  
  36. /**
  37.  *
  38.  * Resource bundles contain locale-specific objects.
  39.  * When your program needs a locale-specific resource,
  40.  * a <code>String</code> for example, your program can load it
  41.  * from the resource bundle that is appropriate for the
  42.  * current user's locale. In this way, you can write
  43.  * program code that is largely independent of the user's
  44.  * locale isolating most, if not all, of the locale-specific
  45.  * information in resource bundles.
  46.  *
  47.  * <p>
  48.  * This allows you to write programs that can:
  49.  * <UL type=SQUARE>
  50.  * <LI> be easily localized, or translated, into different languages
  51.  * <LI> handle multiple locales at once
  52.  * <LI> be easily modified later to support even more locales
  53.  * </UL>
  54.  *
  55.  * <P>
  56.  * One resource bundle is, conceptually, a set of related classes that
  57.  * inherit from <code>ResourceBundle</code>. Each related subclass of
  58.  * <code>ResourceBundle</code> has the same base name plus an additional
  59.  * component that identifies its locale. For example, suppose your resource
  60.  * bundle is named <code>MyResources</code>. The first class you are likely
  61.  * to write is the default resource bundle which simply has the same name as
  62.  * its family--<code>MyResources</code>. You can also provide as
  63.  * many related locale-specific classes as you need: for example, perhaps
  64.  * you would provide a German one named <code>MyResources_de</code>.
  65.  *
  66.  * <P>
  67.  * Each related subclass of <code>ResourceBundle</code> contains the same
  68.  * items, but the items have been translated for the locale represented by that
  69.  * <code>ResourceBundle</code> subclass. For example, both <code>MyResources</code>
  70.  * and <code>MyResources_de</code> may have a <code>String</code> that's used
  71.  * on a button for confirming operations. In <code>MyResources</code> the
  72.  * <code>String</code> may contain <code>OK</code> and in
  73.  * <code>MyResources_de</code> it may contain <code>Gut</code>.
  74.  *
  75.  * <P>
  76.  * If there are different resources for different countries, you
  77.  * can make specializations: for example, <code>MyResources_de_CH</code>
  78.  * for Switzerland. If you want to only modify some of the resources
  79.  * in the specialization, you can do so.
  80.  *
  81.  * <P>
  82.  * When your program needs a locale-specific object, it loads
  83.  * the <code>ResourceBundle</code> class using the <code>getBundle</code>
  84.  * method:
  85.  * <blockquote>
  86.  * <pre>
  87.  * ResourceBundle myResources =
  88.  *      ResourceBundle.getBundle("MyResources", currentLocale);
  89.  * </pre>
  90.  * </blockquote>
  91.  * The first argument specifies the family name of the resource
  92.  * bundle that contains the object in question. The second argument
  93.  * indicates the desired locale. <code>getBundle</code>
  94.  * uses these two arguments to construct the name of the
  95.  * <code>ResourceBundle</code> subclass it should load as follows.
  96.  *
  97.  * <P>
  98.  * The resource bundle lookup searches for classes with various suffixes
  99.  * on the basis of (1) the desired locale and (2) the default locale (baseclass),
  100.  * in the following order from lower-level (more specific) to parent-level
  101.  * (less specific):
  102.  * <p> baseclass + "_" + language1 + "_" + country1 + "_" + variant1
  103.  * <BR> baseclass + "_" + language1 + "_" + country1
  104.  * <BR> baseclass + "_" + language1
  105.  * <BR> baseclass
  106.  * <BR> baseclass + "_" + language2 + "_" + country2 + "_" + variant2
  107.  * <BR> baseclass + "_" + language2 + "_" + country2
  108.  * <BR> baseclass + "_" + language2
  109.  *
  110.  * <P>
  111.  * The result of the lookup is a class, but that class may be
  112.  * backed by a property file on disk. If a lookup fails,
  113.  * <code>getBundle()</code> throws a <code>MissingResourceException</code>.
  114.  *
  115.  * <P>
  116.  * The baseclass <strong>must</strong> be fully
  117.  * qualified (for example, <code>myPackage.MyResources</code>, not just
  118.  * <code>MyResources</code>). It must
  119.  * also be accessable by your code; it cannot be a class that is private
  120.  * to the package where <code>ResourceBundle.getBundle</code> is called.
  121.  *
  122.  * <P>
  123.  * Note: <code>ResourceBundle</code> are used internally in accessing
  124.  * <code>NumberFormat</code>s, <code>Collation</code>s, and so on.
  125.  * The lookup strategy is the same.
  126.  *
  127.  * <P>
  128.  * Resource bundles contain key/value pairs. The keys uniquely
  129.  * identify a locale-specific object in the bundle. Here's an
  130.  * example of a <code>ListResourceBundle</code> that contains
  131.  * two key/value pairs:
  132.  * <blockquote>
  133.  * <pre>
  134.  * class MyResource extends ListResourceBundle {
  135.  *      public Object[][] getContents() {
  136.  *              return contents;
  137.  *      }
  138.  *      static final Object[][] contents = {
  139.  *      // LOCALIZE THIS
  140.  *              {"OkKey", "OK"},
  141.  *              {"CancelKey", "Cancel"},
  142.  *      // END OF MATERIAL TO LOCALIZE
  143.  *      };
  144.  * }
  145.  * </pre>
  146.  * </blockquote>
  147.  * Keys are always <code>String</code>s.
  148.  * In this example, the keys are <code>OkKey</code> and <code>CancelKey</code>.
  149.  * In the above example, the values
  150.  * are also <code>String</code>s--<code>OK</code> and <code>Cancel</code>--but
  151.  * they don't have to be. The values can be any type of object.
  152.  *
  153.  * <P>
  154.  * You retrieve an object from resource bundle using the appropriate
  155.  * getter method. Because <code>OkKey</code> and <code>CancelKey</code>
  156.  * are both strings, you would use <code>getString</code> to retrieve them:
  157.  * <blockquote>
  158.  * <pre>
  159.  * button1 = new Button(myResourceBundle.getString("OkKey"));
  160.  * button2 = new Button(myResourceBundle.getString("CancelKey"));
  161.  * </pre>
  162.  * </blockquote>
  163.  * The getter methods all require the key as an argument and return
  164.  * the object if found. If the object is not found, the getter method
  165.  * throws a <code>MissingResourceException</code>.
  166.  *
  167.  * <P>
  168.  * Besides <code>getString</code>; ResourceBundle supports a number
  169.  * of other methods for getting different types of objects such as
  170.  * <code>getStringArray</code>. If you don't have an object that
  171.  * matches one of these methods, you can use <code>getObject</code>
  172.  * and cast the result to the appropriate type. For example:
  173.  * <blockquote>
  174.  * <pre>
  175.  * int[] myIntegers = (int[]) myResources.getObject("intList");
  176.  * </pre>
  177.  * </blockquote>
  178.  *
  179.  * <P>
  180.  * <STRONG>NOTE:</STRONG> You should always supply a baseclass with
  181.  * no suffixes. This will be the class of "last resort", if a locale
  182.  * is requested that does not exist. For example, below we have a class
  183.  * <code>MyResources</code>. It happens to contain US strings,
  184.  * so we don't have to have an explicit <code>MyResource_en</code> or
  185.  * <code>MyResource_en_US</code>.
  186.  *
  187.  * <P>
  188.  * The JDK provides two subclasses of <code>ResourceBundle</code>,
  189.  * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
  190.  * that provide a fairly simple way to create resources. (Once serialization
  191.  * is fully integrated, we will provide another
  192.  * way.) As you saw briefly in a prevous example, <code>ListResourceBundle</code>
  193.  * manages its resource as a List of key/value pairs.
  194.  * <code>PropertyResourceBundle</code> uses a properties file to manage
  195.  * its resources.
  196.  *
  197.  * <p>
  198.  * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
  199.  * do not suit your needs, you can write your own <code>ResourceBundle</code>
  200.  * subclass.  Your subclasses must overrde two methods: <code>handleGetObject</code>
  201.  * and <code>getKeys()</code>.
  202.  *
  203.  * <P>
  204.  * The following is a very simple example of a <code>ResourceBundle</code> subclass
  205.  * that manages only a few resources (for a larger number of resources
  206.  * you would probably use a <code>Hashtable</code>). Notice that if the key
  207.  * is not found, <code>handleGetObject</code> must return null. Notice also
  208.  * that you don't need to supply a value if a "parent-level"
  209.  * <code>ResourceBundle</code> handles the same
  210.  * key with the same value (look at uk below).
  211.  * <strong><p>Example:</strong>
  212.  * <blockquote>
  213.  * <pre>
  214.  * abstract class MyResources extends ResourceBundle {
  215.  *     public Object handleGetObject(String key) {
  216.  *         if (key.equals("okKey")) return "Ok";
  217.  *         if (key.equals("cancelKey")) return "Cancel";
  218.  *        return null;
  219.  *     }
  220.  * }
  221.  *
  222.  * abstract class MyResources_de extends MyResources {
  223.  *     public Object handleGetObject(String key) {
  224.  *         if (key.equals("okKey")) return "Gut";
  225.  *         if (key.equals("cancelKey")) return "Vernichten";
  226.  *         return null;
  227.  *     }
  228.  * }
  229.  *
  230.  * abstract class MyResources_uk extends MyResources {
  231.  *     public Object handleGetObject(String key) {
  232.  *         // don't need okKey, since parent level handles it.
  233.  *         if (key.equals("cancelKey")) return "Dispose";
  234.  *            return null;
  235.  *     }
  236.  * }
  237.  * </pre>
  238.  * </blockquote>
  239.  * You do not have to restrict yourself to using a single family of
  240.  * <code>ResourceBundle</code>s. For example, you could have a set of bundles for
  241.  * exception messages, <code>ExceptionResources</code>
  242.  * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
  243.  * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
  244.  * <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
  245.  *
  246.  * @see ListResourceBundle
  247.  * @see PropertyResourceBundle
  248.  * @see MissingResourceException
  249.  */
  250. abstract public class ResourceBundle {
  251.  
  252.     /**
  253.      * Get an object from a ResourceBundle.
  254.      * <BR>Convenience method to save casting.
  255.      * @param key see class description.
  256.      */
  257.     public final String getString(String key) throws MissingResourceException {
  258.         return (String) getObject(key);
  259.     }
  260.  
  261.     /**
  262.      * Get an object from a ResourceBundle.
  263.      * <BR>Convenience method to save casting.
  264.      * @param key see class description.
  265.      */
  266.     public final String[] getStringArray(String key)
  267.         throws MissingResourceException {
  268.         return (String[]) getObject(key);
  269.     }
  270.  
  271.     /**
  272.      * Get an object from a ResourceBundle.
  273.      * @param key see class description.
  274.      */
  275.     public final Object getObject(String key) throws MissingResourceException {
  276.         Object obj = handleGetObject(key);
  277.         if (obj == null) {
  278.             if (parent != null) {
  279.                 obj = parent.getObject(key);
  280.             }
  281.             if (obj == null)
  282.                 throw new MissingResourceException("Can't find resource",
  283.                                                    this.getClass().getName(),
  284.                                                    key);
  285.         }
  286.         return obj;
  287.     }
  288.  
  289.  
  290.     /**
  291.      * Get the appropriate ResourceBundle subclass.
  292.      * @param baseName see class description.
  293.      */
  294.     public static final ResourceBundle getBundle(String baseName)
  295.         throws MissingResourceException
  296.     {
  297.         return getBundle(baseName, Locale.getDefault(),
  298.         /* must determine loader here, else we break stack invariant */
  299.         getLoader());
  300.     }
  301.  
  302.  
  303.     /**
  304.      * Get the appropriate ResourceBundle subclass.
  305.      * @param baseName see class description.
  306.      * @param locale   see class description.
  307.      */
  308.     public static final ResourceBundle getBundle(String baseName,
  309.                                                          Locale locale)
  310.     {
  311.     return getBundle(baseName, locale, getLoader());
  312.     }
  313.  
  314.     /*
  315.      * Automatic determination of the ClassLoader to be used to load
  316.      * resources on behalf of the client.  N.B. The client is getLoader's
  317.      * caller's caller.
  318.      */
  319.     private static ClassLoader getLoader() {
  320.     Class[] stack = getClassContext();
  321.     /* Magic number 2 identifies our caller's caller */
  322.     Class c = stack[2];
  323.     ClassLoader cl = (c == null) ? null : c.getClassLoader();
  324.     return (cl == null) ? systemClassLoader : cl;
  325.     }
  326.     private static native Class[] getClassContext();
  327.     private static SystemClassLoader systemClassLoader =
  328.         new SystemClassLoader();
  329.  
  330.     /**
  331.      * Get the appropriate ResourceBundle subclass.
  332.      * @param baseName see class description.
  333.      * @param locale see class description.
  334.      */
  335.     private static synchronized ResourceBundle
  336.         getBundle(String baseName, Locale locale, ClassLoader loader)
  337.         throws MissingResourceException
  338.     {
  339.         StringBuffer localeName
  340.             = new StringBuffer("_").append(locale.toString());
  341.         if (locale.toString().equals(""))
  342.             localeName.setLength(0);
  343.  
  344.         ResourceBundle lookup = findBundle(baseName,localeName,loader,false);
  345.         if(lookup == null) {
  346.             localeName.setLength(0);
  347.             localeName.append("_").append( Locale.getDefault().toString() );
  348.             lookup = findBundle(baseName, localeName, loader, true);
  349.             if( lookup == null ) {
  350.                 throw new MissingResourceException("can't find resource for "
  351.                                                    + baseName + "_" + locale,
  352.                                                    baseName + "_" + locale,"");
  353.             }
  354.         }
  355.  
  356.         // Setup lookup's ancestry. If we find an ancestor whose parent is null,
  357.         // we set up the ancestor's parent as well.
  358.         ResourceBundle child = lookup;
  359.         while( (child != null) && (child.parent == null) ) {
  360.             // Chop off the last component of the locale name and search for that
  361.             // as the parent locale.  Use it to set the parent of current child.
  362.             int lastUnderbar = localeName.toString().lastIndexOf('_');
  363.             if( lastUnderbar != -1 ) {
  364.                 localeName.setLength(lastUnderbar);
  365. //                debug("Searching for parent " + baseName + localeName);
  366.                 child.setParent( findBundle(baseName,localeName,loader,true) );
  367.             }
  368.             child = child.parent;
  369.         }
  370.  
  371.         return lookup;
  372.     }
  373.  
  374.  
  375.     /**
  376.      * Set the parent bundle of this bundle.  The parent bundle is
  377.      * searched by getObject when this bundle does not contain a
  378.      * particular resource.
  379.      * @param parent this bundle's parent bundle.
  380.      */
  381.     protected void setParent( ResourceBundle parent ) {
  382.         this.parent = parent;
  383.     }
  384.  
  385.  
  386.     /**
  387.      * The internal routine that does the real work of finding and loading
  388.      * the right ResourceBundle for a given name and locale.
  389.      */
  390.     private static ResourceBundle findBundle(String baseName,
  391.                                              StringBuffer localeName,
  392.                                              ClassLoader loader,
  393.                                              boolean includeBase)
  394.     {
  395.         String localeStr = localeName.toString();
  396.         String baseFileName = baseName.replace('.', '/');
  397.         Object lookup = null;
  398.         String searchName;
  399.         Vector cacheCandidates = new Vector();
  400.         int lastUnderbar;
  401.         InputStream stream;
  402.  
  403.         searchLoop:
  404.         while (true) {
  405.             searchName = baseName + localeStr;
  406.         String cacheName =
  407.         "["+Integer.toString(loader.hashCode())+"]" + searchName;
  408.  
  409.             // First, look in the cache.  We may either find the bundle we're
  410.             // looking for or we may find that the bundle was not found by a
  411.             // previous search.
  412.             lookup = cacheList.get(cacheName);
  413.             if( lookup == NOTFOUND ) {
  414. //                debug("Found " + searchName + " in cache as NOTFOUND");
  415.                 localeName.setLength(0);
  416.                 break searchLoop;
  417.             }
  418.             if( lookup != null ) {
  419. //                debug("Found " + searchName + " in cache");
  420.                 localeName.setLength(0);
  421.                 break searchLoop;
  422.             }
  423.             cacheCandidates.addElement( cacheName );
  424.  
  425.             // Next search for a class
  426. //            debug("Searching for " + searchName );
  427.             try {
  428.                 lookup = loader.loadClass(searchName).newInstance();
  429.                 break searchLoop;
  430.             } catch( Exception e ){}
  431.  
  432.             // Next search for a Properties file.
  433.             searchName = baseFileName + localeStr + ".properties";
  434. //            debug("Searching for " + searchName );
  435.             stream = loader.getResourceAsStream(searchName);
  436.             if( stream != null ) {
  437.         // make sure it is buffered
  438.         stream = new java.io.BufferedInputStream(stream);
  439.                 try {
  440.                     lookup = (Object)new PropertyResourceBundle( stream );
  441.                     break searchLoop;
  442.                 } catch (Exception e) {}
  443.             }
  444.  
  445.             //Chop off the last part of the locale name string and try again.
  446.             lastUnderbar = localeStr.lastIndexOf('_');
  447.             if( ((lastUnderbar==0)&&(!includeBase)) || (lastUnderbar == -1) ) {
  448.                 break;
  449.             }
  450.             localeStr = localeStr.substring(0,lastUnderbar);
  451.             localeName.setLength(lastUnderbar);
  452.         }
  453.  
  454.  
  455.         if( lookup != null ) {
  456.             // Add a positive result to the cache. The result may include
  457.             // NOTFOUND
  458.             for( int i=0; i<cacheCandidates.size(); i++ ) {
  459.                 cacheList.put(cacheCandidates.elementAt(i), lookup);
  460. //                debug("Adding " + cacheCandidates.elementAt(i) + " to cache"
  461. //                      + ((lookup == NOTFOUND)?" as NOTFOUND.":"."));
  462.             }
  463.         }
  464.         else {
  465.             // If we searched all the way to the base, then we can add
  466.             // the NOTFOUND result to the cache.  Otherwise we can say
  467.             // nothing.
  468.             if( includeBase == true ) {
  469.                 for( int i=0; i<cacheCandidates.size(); i++ ) {
  470.                     cacheList.put(cacheCandidates.elementAt(i), NOTFOUND);
  471. //                    debug("Adding " + cacheCandidates.elementAt(i)
  472. //                          + " to cache as NOTFOUND.");
  473.                 }
  474.             }
  475.         }
  476.  
  477.         if( (lookup == NOTFOUND) || (lookup == null) )
  478.             return null;
  479.         else
  480.             return (ResourceBundle)lookup;
  481.     }
  482.  
  483.  
  484.     /** Get an object from a ResourceBundle.
  485.      * <STRONG>NOTE: </STRONG>Subclasses must override.
  486.      * @param key see class description.
  487.      */
  488.     protected abstract Object handleGetObject(String key)
  489.         throws MissingResourceException;
  490.  
  491.     /**
  492.      * Return an enumeration of the keys.
  493.      * <STRONG>NOTE: </STRONG>Subclasses must override.
  494.      */
  495.     public abstract Enumeration getKeys();
  496.  
  497.     /**
  498.      * For printf debugging.
  499.      */
  500.     private static boolean debugFlag = false;
  501.     private static void debug(String str) {
  502.         if( debugFlag ) {
  503.             System.out.println("ResourceBundle: " + str);
  504.         }
  505.     }
  506.  
  507.     /**
  508.      * The parent bundle is consulted by getObject when this bundle
  509.      * does not contain a particular resource.
  510.      */
  511.     protected ResourceBundle parent = null;
  512.  
  513.     private static final Integer NOTFOUND = new Integer(-1);
  514.     private static Hashtable cacheList = new Hashtable();
  515. }
  516.  
  517.  
  518. /**
  519.  * The SystemClassLoader loads system classes (those in your classpath).
  520.  * This is an attempt to unify the handling of system classes and ClassLoader
  521.  * classes.
  522.  */
  523. class SystemClassLoader extends java.lang.ClassLoader {
  524.  
  525.     protected Class loadClass( String name, boolean resolve )
  526.         throws ClassNotFoundException
  527.     {
  528.         return findSystemClass( name );
  529.     }
  530.  
  531.     public InputStream getResourceAsStream(String name) {
  532.         return ClassLoader.getSystemResourceAsStream(name);
  533.     }
  534.  
  535.     public java.net.URL getResource(String name) {
  536.         return ClassLoader.getSystemResource(name);
  537.     }
  538. }
  539.